﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BomberManServer.Gaming.Object.Bonuses;
using BomberManServer.Gaming.Object.Mapping.Model;
using States = BomberManServer.Gaming.Object.Mapping.Model.States;
using BomberManServer.Gaming.Object.Mapping.Items;
using BomberManServer.Utils;

namespace BomberManServer.Gaming.Object.Mapping
{
    /// <summary>
    /// This class will generate blocks for you
    /// You have to determine the number of bonus of each type
    /// The drop chance will be calculated to maintain density
    /// </summary>
    public class Zone : InGame, IBuilder
    {
        // optimization
        private List<States.BonusBlock> states;

        // the blocks that are in the zone
        private List<Model.Block> blocks;

        // how many bonus of each type you will find in this zone
        private uint nb_bonus;
        private uint drop_chance;
        private static uint nb_bonustypes = (uint)((int) Bonus.Type.SIZE - Constants.instance.onlyTempType);

        private uint nb_max;

        private Logger.Handler log;
        private MersenneTwister random;

        public Zone(UInt16 nb_bonus, Game game) : base(game)
        {
            this.nb_bonus = nb_bonus;
            states = new List<States.BonusBlock>();
            blocks = new List<Model.Block>();

            random = game.random;
            log = game.logger.createHandler("Zone");
            log.information("Instanciated");
        }

        public void computeDropChance()
        {
            uint drop_chance = 0;
            uint sum_bonus = (nb_bonustypes * nb_bonus);

            // look for non coherent data
            if( (nb_bonustypes * nb_bonus) > blocks.Count) throw new ArithmeticException();

            // estimate perfect drop chance
            drop_chance = (uint) blocks.Count / sum_bonus;
            uint density = computeDensity(drop_chance, nb_bonus);

            // check if density is okay
            if( (density >= Constants.instance.densityMin) && (density <= Constants.instance.densityMax) )
                this.drop_chance = drop_chance;

            // density is not okay
            // lets find the problem
            int modifier;
            if (density < Constants.instance.densityMin)
                modifier = +1;
            else
                modifier = -1;

            // lets try to modify drop_chance
            density = computeDensity( (uint)(drop_chance + modifier), nb_bonus);

            // check if density is okay
            if ((density >= Constants.instance.densityMin) && (density <= Constants.instance.densityMax))
                this.drop_chance = ( (uint)(drop_chance + modifier) );

            // lets try to modify nb_bonus
            density = computeDensity(drop_chance, (uint) (nb_bonus + modifier) );

            // check if density is okay
            if ((density >= Constants.instance.densityMin) && (density <= Constants.instance.densityMax))
            {
                this.drop_chance = ( (uint) (drop_chance + modifier) );
                nb_bonus = (uint) (nb_bonus + modifier);
            }
            //log.information("Drop chance is : " + drop_chance.ToString());
        }

        private uint computeDensity(uint drop_chance, uint nb_bonus)
        {
            uint sum_bonus = (nb_bonustypes * nb_bonus);
            uint total_blocks = drop_chance * sum_bonus;
            // we have size > total_blocks
            return (100 * total_blocks / (uint)blocks.Count);
        }

        public void attach(Model.Block block)
        {
            blocks.Add(block);
        }

        // call this when all blocks are registred
        public void initialize()
        {
            computeDropChance();
            nb_max = drop_chance * nb_bonus;
            for (int index = 0; index < nb_bonustypes; index++)
                states.Add(new States.BonusBlock(this, new Items.BonusBlock(game, nb_max,nb_bonus,(Items.Bonus.Type)index)));
        }

        private Model.Block pickBlock(List<Model.Block> blocks)
        {
            int index = (int)random.NextUInt((uint)(blocks.Count - 1));
            //log.information("Choosen index : " + index);
            Model.Block block = blocks[index];
            blocks.RemoveAt(index);
            return block;
        }

        void IBuilder.reset()
        {
            //log.information("Blocks in the zone : " + nb_max * (int)Bonus.Type.SIZE);

            List<Model.Block> blocks_available = new List<Model.Block>(blocks);

            foreach (States.BonusBlock state in states)
                state.item.reset();

            for (int index = 0; index < nb_max; index++)
            {
                for (int bonus_type = 0; bonus_type < nb_bonustypes; bonus_type++)
                    pickBlock(blocks_available).state = states[bonus_type];

            }
            foreach(Model.Block block in blocks_available)
                block.state = Constants.instance.stateFree;
        }
    }
}
